#define _GNU_SOURCE
#include <X11/Xft/Xft.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xinerama.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>

#define SchemeNorm 0
#define SchemeSel 1
#define SchemeDir 2
#define SchemeLast 3

static const char *colors[SchemeLast][2] = {
    [SchemeNorm] = {"#ffffff", "#222222"},
    [SchemeSel] = {"#000000", "#ffff00"},
    [SchemeDir] = {"#00ff00", "#222222"},
};

static const char *font_names[] = {
    "WenQuanYi Micro Hei:size=14:antialias=true",
    "Noto Sans CJK SC:size=14:antialias=true",
    "Microsoft YaHei:size=14:antialias=true",
    "SimHei:size=14:antialias=true",
    "DejaVu Sans Mono:size=14:antialias=true",
    "Monaco:size=14:antialias=true",
    "Consolas:size=14:antialias=true",
    "monospace:size=14:antialias=true", NULL};

static unsigned int win_w = 1280, win_h = 720;
static int curr_offset = 0;
static int sel_idx = 0;
static int items_per_page = 0;

typedef struct {
    char name[NAME_MAX];
    char path[PATH_MAX];
    int is_dir;
} Entry;
static Entry *entries = NULL;
static int entry_count = 0;

static char current_dir[PATH_MAX];
static char history[64][PATH_MAX];
static int history_len = 0;

static int compare_entries(const void *a, const void *b) {
    Entry *ea = (Entry*)a;
    Entry *eb = (Entry*)b;
    if (ea->is_dir && !eb->is_dir) return -1;
    if (!ea->is_dir && eb->is_dir) return 1;
    return strcmp(ea->name, eb->name);
}

static void load_directory(const char *dir) {
    if (entries) free(entries);
    entries = NULL;
    entry_count = 0;

    DIR *dp = opendir(dir);
    if (!dp) return;

    struct dirent *dentry;
    while ((dentry = readdir(dp)) && entry_count < 4096) {
        if (dentry->d_name[0] == '.' && 
            strcmp(dentry->d_name, ".") != 0 && 
            strcmp(dentry->d_name, "..") != 0)
            continue;

        char fullpath[PATH_MAX];
        snprintf(fullpath, PATH_MAX, "%s/%s", dir, dentry->d_name);

        struct stat st;
        int is_dir = 0;
        if (stat(fullpath, &st) == 0)
            is_dir = S_ISDIR(st.st_mode);

        entries = realloc(entries, (entry_count + 1) * sizeof(Entry));
        strncpy(entries[entry_count].name, dentry->d_name, NAME_MAX);
        strncpy(entries[entry_count].path, fullpath, PATH_MAX);
        entries[entry_count].is_dir = is_dir;
        entry_count++;
    }
    closedir(dp);
    qsort(entries, entry_count, sizeof(Entry), compare_entries);
    sel_idx = 0;
    curr_offset = 0;
}

static void enter_directory(const char *dir) {
    if (history_len == 0 || strcmp(current_dir, history[history_len-1]) != 0) {
        if (history_len < 64)
            strncpy(history[history_len++], current_dir, PATH_MAX);
    }
    
    realpath(dir, current_dir);
    load_directory(current_dir);
}

static void go_back() {
    if (history_len > 0) {
        strncpy(current_dir, history[--history_len], PATH_MAX);
        load_directory(current_dir);
    }
}

typedef struct {
    Display *dpy;
    unsigned int h;
    XftFont *xfont;
} Fnt;

enum { ColFg, ColBg };
typedef XftColor Clr;

typedef struct {
    unsigned int w, h;
    Display *dpy;
    int screen;
    Window root;
    Drawable drawable;
    GC gc;
    Clr *scheme;
    Fnt *font;
} Drw;

Drw *drw_create(Display *dpy, int screen, Window root) {
    Drw *drw = calloc(1, sizeof(Drw));
    if (!drw) return NULL;
    drw->dpy = dpy;
    drw->screen = screen;
    drw->root = root;
    drw->w = win_w;
    drw->h = win_h;
    drw->drawable = XCreatePixmap(dpy, root, drw->w, drw->h, DefaultDepth(dpy, screen));
    drw->gc = XCreateGC(dpy, root, 0, NULL);
    return drw;
}

Fnt *font_create(Drw *drw) {
    if (!drw) return NULL;
    Fnt *f = calloc(1, sizeof(Fnt));
    if (!f) return NULL;
    f->dpy = drw->dpy;
    XftFont *xfont = NULL;
    for (int i = 0; font_names[i]; i++) {
        xfont = XftFontOpenName(drw->dpy, drw->screen, font_names[i]);
        if (xfont) break;
    }
    if (!xfont) {
        fprintf(stderr, "Warning: Using default font\n");
        xfont = XftFontOpenName(drw->dpy, drw->screen, "monospace:size=12");
        if (!xfont) {
            fprintf(stderr, "Error: No font\n");
            free(f);
            return NULL;
        }
    }
    f->xfont = xfont;
    f->h = xfont->ascent + xfont->descent;
    return f;
}

void drw_clr_create(Drw *drw, Clr *dest, const char *name) {
    if (!drw || !dest) return;
    if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
                         DefaultColormap(drw->dpy, drw->screen), name, dest))
        fprintf(stderr, "Cannot allocate color: %s\n", name);
}

void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled) {
    if (!drw) return;
    XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
    if (filled) XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
    else XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
}

void drw_text(Drw *drw, int x, int y, const char *text) {
    if (!drw || !text) return;
    int ty = y + (drw->font->h / 2) + (drw->font->xfont->ascent / 2);

    XGlyphInfo ext;
    XftTextExtentsUtf8(drw->dpy, drw->font->xfont, (XftChar8 *)text,
                     strlen(text), &ext);
    if ((unsigned int)ext.xOff > drw->w - 40) {
        char truncated[1024];
        strncpy(truncated, text, 50);
        truncated[50] = '\0';
        strcat(truncated, "...");
        XftDraw *d = XftDrawCreate(drw->dpy, drw->drawable,
                                 DefaultVisual(drw->dpy, drw->screen),
                                 DefaultColormap(drw->dpy, drw->screen));
        XftDrawStringUtf8(d, &drw->scheme[ColFg], drw->font->xfont, x, ty,
                      (XftChar8 *)truncated, strlen(truncated));
        XftDrawDestroy(d);
    } else {
        XftDraw *d = XftDrawCreate(drw->dpy, drw->drawable,
                                 DefaultVisual(drw->dpy, drw->screen),
                                 DefaultColormap(drw->dpy, drw->screen));
        XftDrawStringUtf8(d, &drw->scheme[ColFg], drw->font->xfont, x, ty,
                      (XftChar8 *)text, strlen(text));
        XftDrawDestroy(d);
    }
}

void drw_refresh(Drw *drw, Window win) {
    if (!drw) return;
    XCopyArea(drw->dpy, drw->drawable, win, drw->gc, 0, 0, drw->w, drw->h, 0, 0);
    XSync(drw->dpy, False);
}

void draw_list(Drw *drw, Clr *norm, Clr *sel, Clr *dir) {
    if (!drw || !norm || !sel || !dir) return;

    int status_bar_h = drw->font->h * 2;
    int list_h = drw->h - status_bar_h;
    items_per_page = list_h / drw->font->h;
    if (items_per_page <= 0) items_per_page = 1;

    drw->scheme = norm;
    drw_rect(drw, 0, 0, drw->w, drw->h, 1);

    int list_y = 0;
    for (int i = curr_offset; i < entry_count && i < curr_offset + items_per_page; i++) {
        if (i == sel_idx) drw->scheme = sel;
        else if (entries[i].is_dir) drw->scheme = dir;
        else drw->scheme = norm;

        drw_rect(drw, 0, list_y, drw->w, drw->font->h, 1);
        char disp_name[NAME_MAX + 2];
        strcpy(disp_name, entries[i].name);
        if (entries[i].is_dir) strcat(disp_name, "/");
        drw_text(drw, 10, list_y, disp_name);
        list_y += drw->font->h;
    }

    drw->scheme = sel;
    drw_rect(drw, 0, drw->h - status_bar_h, drw->w, status_bar_h, 1);
    drw->scheme = sel;
    drw_text(drw, 10, drw->h - status_bar_h, current_dir);
    char help[512] = "Enter=Enter dir | Backspace=Go back | Click=Select file/dir | q=Quit | Path copied to clipboard";
    drw_text(drw, 10, drw->h - status_bar_h + drw->font->h, help);
}

static void copy_to_clipboard(const char *text) {
    char cmd[PATH_MAX + 64];
    if (access("/usr/bin/xclip", X_OK) == 0) {
        snprintf(cmd, sizeof(cmd), "echo -n '%s' | xclip -selection clipboard", text);
        system(cmd);
    } else if (access("/usr/bin/wl-copy", X_OK) == 0) {
        snprintf(cmd, sizeof(cmd), "echo -n '%s' | wl-copy", text);
        system(cmd);
    }
}

void handle_mouse(Drw *drw, Clr *norm, Clr *sel, Clr *dir, XButtonEvent *ev) {
    if (!drw || !ev) return;

    if (ev->button == 4) {
        sel_idx = (sel_idx == 0) ? entry_count - 1 : sel_idx - 1;
        curr_offset = sel_idx - (sel_idx % items_per_page);
    } else if (ev->button == 5) {
        sel_idx = (sel_idx == entry_count - 1) ? 0 : sel_idx + 1;
        curr_offset = sel_idx - (sel_idx % items_per_page);
    }
    else if (ev->type == ButtonPress) {
        int click_idx = curr_offset + (ev->y / drw->font->h);
        if (click_idx >= 0 && click_idx < entry_count) {
            Entry *e = &entries[click_idx];
            if (e->is_dir) {
                enter_directory(e->path);
            } else {
                printf("%s\n", e->path);
                copy_to_clipboard(e->path);
                exit(0);
            }
        }
    }

    draw_list(drw, norm, sel, dir);
    drw_refresh(drw, ev->window);
}

void event_loop(Display *dpy, Window win, Drw *drw, Clr *norm, Clr *sel, Clr *dir) {
    XEvent ev;
    while (1) {
        XNextEvent(dpy, &ev);
        switch (ev.type) {
        case Expose:
            if (ev.xexpose.count == 0) {
                draw_list(drw, norm, sel, dir);
                drw_refresh(drw, win);
            }
            break;
        case ButtonPress:
            handle_mouse(drw, norm, sel, dir, &ev.xbutton);
            break;
        case KeyPress: {
            KeySym ks = XLookupKeysym(&ev.xkey, 0);
            if (ks == XK_Escape || ks == XK_q) {
                exit(0);
            } else if (ks == XK_Return) {
                if (sel_idx >= 0 && sel_idx < entry_count) {
                    Entry *e = &entries[sel_idx];
                    if (e->is_dir) {
                        enter_directory(e->path);
                    } else {
                        printf("%s\n", e->path);
                        copy_to_clipboard(e->path);
                        exit(0);
                    }
                }
            } else if (ks == XK_BackSpace) {
                go_back();
            } else if (ks == XK_Up) {
                sel_idx = (sel_idx == 0) ? entry_count - 1 : sel_idx - 1;
                curr_offset = sel_idx - (sel_idx % items_per_page);
            } else if (ks == XK_Down) {
                sel_idx = (sel_idx == entry_count - 1) ? 0 : sel_idx + 1;
                curr_offset = sel_idx - (sel_idx % items_per_page);
            }
            draw_list(drw, norm, sel, dir);
            drw_refresh(drw, win);
            break;
        }
        case DestroyNotify:
            exit(0);
        }
    }
}

Window create_window(Display *dpy) {
    if (!dpy) return 0;
    int screen = DefaultScreen(dpy);
    Window root = RootWindow(dpy, screen);
    XSetWindowAttributes swa;

    int x = 0, y = 0;
    XineramaScreenInfo *info;
    int n;
    if ((info = XineramaQueryScreens(dpy, &n))) {
        XWindowAttributes wa;
        XGetWindowAttributes(dpy, root, &wa);
        x = (wa.width - win_w) / 2;
        y = (wa.height - win_h) / 2;
        XFree(info);
    }

    swa.override_redirect = True;
    swa.background_pixel = 0;
    swa.event_mask = ExposureMask | ButtonPressMask | DestroyNotify | KeyPressMask;
    Window win = XCreateWindow(dpy, root, x, y, win_w, win_h, 0, DefaultDepth(dpy, screen),
                            InputOutput, DefaultVisual(dpy, screen),
                            CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
    XMapRaised(dpy, win);
    XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
    return win;
}

void usage() {
    fprintf(stderr, "Usage: guifm [OPTIONS]\n");
    fprintf(stderr, "Simple GUI file manager (start at $HOME)\n");
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -h        Show help\n");
    fprintf(stderr, "  -w <width>  Window width (default: 1280)\n");
    fprintf(stderr, "  -H <height> Window height (default: 720)\n");
    exit(0);
}

int main(int argc, char **argv) {
    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "-h")) usage();
        else if (!strcmp(argv[i], "-w") && i + 1 < argc) win_w = atoi(argv[++i]);
        else if (!strcmp(argv[i], "-H") && i + 1 < argc) win_h = atoi(argv[++i]);
        else usage();
    }

    char *home_dir = getenv("HOME");
    if (home_dir && strlen(home_dir) > 0) {
        strncpy(current_dir, home_dir, PATH_MAX);
    } else {
        strcpy(current_dir, "/");
    }
    load_directory(current_dir);

    Display *dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "Error: Cannot open X display\n");
        return 1;
    }

    int screen = DefaultScreen(dpy);
    Window root = RootWindow(dpy, screen);
    Drw *drw = drw_create(dpy, screen, root);
    if (!drw) {
        fprintf(stderr, "Error: Cannot create drawer\n");
        XCloseDisplay(dpy);
        return 1;
    }
    drw->font = font_create(drw);
    if (!drw->font) {
        fprintf(stderr, "Error: Cannot load font\n");
        XCloseDisplay(dpy);
        free(drw);
        return 1;
    }

    Clr norm[2], sel[2], dir[2];
    drw_clr_create(drw, &norm[ColFg], colors[SchemeNorm][ColFg]);
    drw_clr_create(drw, &norm[ColBg], colors[SchemeNorm][ColBg]);
    drw_clr_create(drw, &sel[ColFg], colors[SchemeSel][ColFg]);
    drw_clr_create(drw, &sel[ColBg], colors[SchemeSel][ColBg]);
    drw_clr_create(drw, &dir[ColFg], colors[SchemeDir][ColFg]);
    drw_clr_create(drw, &dir[ColBg], colors[SchemeDir][ColBg]);

    Window win = create_window(dpy);
    if (!win) {
        fprintf(stderr, "Error: Cannot create window\n");
        XCloseDisplay(dpy);
        free(drw->font);
        free(drw);
        return 1;
    }

    draw_list(drw, norm, sel, dir);
    drw_refresh(drw, win);
    event_loop(dpy, win, drw, norm, sel, dir);

    free(entries);
    XCloseDisplay(dpy);
    free(drw->font);
    free(drw);
    return 0;
}

